package com.sheepone.distributor.autoconfigure;

import com.lmax.disruptor.EventTranslator;
import com.sheepone.distributor.Distributor;
import com.sheepone.distributor.anno.StationEventListener;
import com.sheepone.distributor.DistributorLine;
import com.sheepone.distributor.WorkerStation;
import com.sheepone.distributor.WorkerStationManager;
import com.sheepone.distributor.event.DistributeEvent;
import com.sheepone.distributor.exeception.AbsentWorkerStation;
import com.sheepone.distributor.exeception.EmptyTopicException;
import com.sheepone.distributor.exeception.IllegalSizeException;
import com.sheepone.distributor.listener.WorkerEventListener;
import com.sheepone.distributor.thread.DelayEventQueue;
import com.sheepone.distributor.util.CglibUtils;
import com.sheepone.distributor.util.NumberUtil;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.event.ContextRefreshedEvent;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;

/**
 * @author Miss.杨
 * @description
 * @since 2023-12-07 21:56
 */
@Configuration
@EnableConfigurationProperties({WorkerStationConfig.class, DistributorLineConfig.class})
public class DistributorAutoConfigure implements ApplicationListener<ContextRefreshedEvent> {
    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        ApplicationContext applicationContext = event.getApplicationContext();
        Map<String, Object> eventListenerMap = applicationContext.getBeansWithAnnotation(StationEventListener.class);
        WorkerStationManager workerLineManager = WorkerStationManager.INSTANCE;
        for (Object eventListener : eventListenerMap.values()) {
            // 这里需要过滤一下代理类，比如用了Sentinel啥的，可能就会对bean做增强，
            // 然后getClass获取到的是一个代理类，代理类上是拿不到Channel注解的
            if (eventListener instanceof WorkerEventListener) {
                Class<?> realClazz = CglibUtils.filterCglibProxyClass(eventListener.getClass());
                StationEventListener listener = realClazz.getAnnotation(StationEventListener.class);
                String station = listener.station();
                if (!station.isEmpty()) {
                    WorkerStation workerStation = workerLineManager.getWorkerStation(station);
                    if (workerStation == null) {
                        throw new AbsentWorkerStation(station);
                    }
                    String topic = listener.topic();
                    if (Objects.equals(topic, "")) {
                        throw new EmptyTopicException();
                    }
                    workerStation.register(topic, (WorkerEventListener) eventListener);
                }
            }
        }
    }

    @Bean
    @ConditionalOnMissingBean
    public Distributor distributor(DistributorLineConfig distributorLineConfig, WorkerStationConfig workerLineConfig) {
        WorkerStationManager workerLineManager = WorkerStationManager.INSTANCE;
        workerLineConfig.getWorkerStations().forEach(config -> {
            workerLineManager.register(config.getStation(), new WorkerStation(config));
        });

        int num = distributorLineConfig.getHandlerNum();// DistributorLine的数量
        ArrayList<DistributorLine> distributorLines = new ArrayList<>(num);
        // 均分后每个DistributorLine的最小的容量，应该为2的整次幂
        int lineConfigSize = distributorLineConfig.getSize();
        String name = distributorLineConfig.getName();
        if (lineConfigSize < 1) {
            throw new IllegalSizeException(name);
        }
        int size = NumberUtil.upper2Pow(lineConfigSize / num);
        DelayEventQueue<EventTranslator<DistributeEvent>> delayEventQueue = new DelayEventQueue<>();
        for (int i = 0; i < num; i++) {
            distributorLines.add(new DistributorLine(name, size, delayEventQueue));
        }
        // 启动 Distributor并注册到IOC容器
        return new Distributor(name, distributorLines).start();
    }
}
